home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / system-config-printer / monitor.py < prev    next >
Encoding:
Python Source  |  2009-05-05  |  27.3 KB  |  724 lines

  1. #!/usr/bin/env python
  2.  
  3. ## Copyright (C) 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
  4. ## Copyright (C) 2007, 2008, 2009 Red Hat, Inc.
  5.  
  6. ## This program is free software; you can redistribute it and/or modify
  7. ## it under the terms of the GNU General Public License as published by
  8. ## the Free Software Foundation; either version 2 of the License, or
  9. ## (at your option) any later version.
  10.  
  11. ## This program is distributed in the hope that it will be useful,
  12. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. ## GNU General Public License for more details.
  15.  
  16. ## You should have received a copy of the GNU General Public License
  17. ## along with this program; if not, write to the Free Software
  18. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  19.  
  20. import cups
  21. import dbus
  22. import dbus.glib
  23. import gobject
  24. import time
  25. from debug import *
  26. import pprint
  27.  
  28. global _
  29. _ = lambda x: x
  30. def set_gettext_function (x):
  31.     _ = x
  32. import statereason
  33. from statereason import StateReason
  34. statereason.set_gettext_function (_)
  35.  
  36. CONNECTING_TIMEOUT = 60 # seconds
  37. MIN_REFRESH_INTERVAL = 1 # seconds
  38.  
  39. def state_reason_is_harmless (reason):
  40.     if (reason.startswith ("moving-to-paused") or
  41.         reason.startswith ("paused") or
  42.         reason.startswith ("shutdown") or
  43.         reason.startswith ("stopping") or
  44.         reason.startswith ("stopped-partly")):
  45.         return True
  46.     return False
  47.  
  48. def collect_printer_state_reasons (connection):
  49.     result = {}
  50.     try:
  51.         printers = connection.getPrinters ()
  52.     except cups.IPPError:
  53.         return result
  54.  
  55.     for name, printer in printers.iteritems ():
  56.         reasons = printer["printer-state-reasons"]
  57.         for reason in reasons:
  58.             if reason == "none":
  59.                 break
  60.             if state_reason_is_harmless (reason):
  61.                 continue
  62.             if not result.has_key (name):
  63.                 result[name] = []
  64.             result[name].append (StateReason (name, reason))
  65.     return result
  66.  
  67. class Watcher:
  68.     # Interface definition
  69.     def monitor_exited (self, monitor):
  70.         debugprint (repr (monitor) + " exited")
  71.  
  72.     def state_reason_added (self, monitor, reason):
  73.         debugprint (repr (monitor) + ": +" + repr (reason))
  74.  
  75.     def state_reason_removed (self, monitor, reason):
  76.         debugprint (repr (monitor) + ": -" + repr (reason))
  77.  
  78.     def still_connecting (self, monitor, reason):
  79.         debugprint (repr (monitor) + ": `%s' still connecting" %
  80.                     reason.get_printer ())
  81.  
  82.     def now_connected (self, monitor, printer):
  83.         debugprint (repr (monitor) + ": `%s' now connected" % printer)
  84.  
  85.     def current_printers_and_jobs (self, monitor, printers, jobs):
  86.         debugprint (repr (monitor) + ": printers and jobs lists provided")
  87.  
  88.     def job_added (self, monitor, jobid, eventname, event, jobdata):
  89.         debugprint (repr (monitor) + ": job %d added" % jobid)
  90.  
  91.     def job_event (self, monitor, jobid, eventname, event, jobdata):
  92.         debugprint (repr (monitor) + ": job %d has event `%s'" %
  93.                     (jobid, eventname))
  94.  
  95.     def job_removed (self, monitor, jobid, eventname, event):
  96.         debugprint (repr (monitor) + ": job %d removed" % jobid)
  97.  
  98.     def printer_added (self, monitor, printer):
  99.         debugprint (repr (monitor) + ": printer `%s' added" % printer)
  100.  
  101.     def printer_event (self, monitor, printer, eventname, event):
  102.         debugprint (repr (monitor) + ": printer `%s' has event `%s'" %
  103.                     (printer, eventname))
  104.  
  105.     def printer_removed (self, monitor, printer):
  106.         debugprint (repr (monitor) + ": printer `%s' removed" % printer)
  107.  
  108.     def cups_connection_error (self, monitor):
  109.         debugprint (repr (monitor) + ": CUPS connection error")
  110.  
  111.     def cups_ipp_error (self, monitor, e, m):
  112.         debugprint (repr (monitor) + ": CUPS IPP error (%d, %s)" %
  113.                     (e, repr (m)))
  114.  
  115. class Monitor:
  116.     # Monitor jobs and printers.
  117.     DBUS_PATH="/com/redhat/PrinterSpooler"
  118.     DBUS_IFACE="com.redhat.PrinterSpooler"
  119.  
  120.     def __init__(self, watcher, bus=None, my_jobs=True, specific_dests=None,
  121.                  monitor_jobs=True, host=None, port=None, encryption=None):
  122.         self.watcher = watcher
  123.         self.my_jobs = my_jobs
  124.         self.specific_dests = specific_dests
  125.         self.monitor_jobs = monitor_jobs
  126.         self.jobs = {}
  127.         self.printer_state_reasons = {}
  128.         self.printers = set()
  129.         self.process_pending_events = True
  130.         self.fetch_jobs_timer = None
  131.  
  132.         if host:
  133.             cups.setServer (host)
  134.         if port:
  135.             cups.setPort (port)
  136.         if encryption:
  137.             cups.setEncryption (encryption)
  138.         self.user = cups.getUser ()
  139.         self.host = cups.getServer ()
  140.         self.port = cups.getPort ()
  141.         self.encryption = cups.getEncryption ()
  142.  
  143.         self.which_jobs = "not-completed"
  144.         self.reasons_seen = {}
  145.         self.connecting_timers = {}
  146.         self.still_connecting = set()
  147.         self.connecting_to_device = {}
  148.         self.received_any_dbus_signals = False
  149.  
  150.         if bus == None:
  151.             try:
  152.                 bus = dbus.SystemBus ()
  153.             except dbus.exceptions.DBusException:
  154.                 # System bus not running.
  155.                 pass
  156.  
  157.         if bus != None:
  158.             bus.add_signal_receiver (self.handle_dbus_signal,
  159.                                      path=self.DBUS_PATH,
  160.                                      dbus_interface=self.DBUS_IFACE)
  161.             self.bus = bus
  162.  
  163.         self.sub_id = -1
  164.         self.refresh ()
  165.  
  166.     def get_jobs (self):
  167.         return self.jobs.copy ()
  168.  
  169.     def cleanup (self):
  170.         if self.sub_id != -1:
  171.             user = cups.getUser ()
  172.             try:
  173.                 cups.setUser (self.user)
  174.                 c = cups.Connection (host=self.host,
  175.                                      port=self.port,
  176.                                      encryption=self.encryption)
  177.                 c.cancelSubscription (self.sub_id)
  178.                 debugprint ("Canceled subscription %d" % self.sub_id)
  179.             except:
  180.                 pass
  181.             cups.setUser (user)
  182.  
  183.         if self.bus != None:
  184.             self.bus.remove_signal_receiver (self.handle_dbus_signal,
  185.                                              path=self.DBUS_PATH,
  186.                                              dbus_interface=self.DBUS_IFACE)
  187.  
  188.         timers = self.connecting_timers.values ()
  189.         for timer in [self.update_timer, self.fetch_jobs_timer]:
  190.             if timer:
  191.                 timers.append (timer)
  192.         for timer in timers:
  193.             gobject.source_remove (timer)
  194.  
  195.         self.watcher.monitor_exited (self)
  196.  
  197.     def set_process_pending (self, whether):
  198.         self.process_pending_events = whether
  199.  
  200.     def check_still_connecting(self, printer):
  201.         """Timer callback to check on connecting-to-device reasons."""
  202.         if not self.process_pending_events:
  203.             # Defer the timer by setting a new one.
  204.             timer = gobject.timeout_add (200, self.check_still_connecting,
  205.                                          printer)
  206.             self.connecting_timers[printer] = timer
  207.             return False
  208.  
  209.         del self.connecting_timers[printer]
  210.         debugprint ("Still-connecting timer fired for `%s'" % printer)
  211.         (printer_jobs, my_printers) = self.sort_jobs_by_printer ()
  212.         self.update_connecting_devices (printer_jobs)
  213.  
  214.         # Don't run this callback again.
  215.         return False
  216.  
  217.     def update_connecting_devices(self, printer_jobs={}):
  218.         """Updates connecting_to_device dict and still_connecting set."""
  219.         time_now = time.time ()
  220.         connecting_to_device = {}
  221.         trouble = False
  222.         for printer, reasons in self.printer_state_reasons.iteritems ():
  223.             connected = True
  224.             for reason in reasons:
  225.                 if reason.get_reason () == "connecting-to-device":
  226.                     have_processing_job = False
  227.                     for job, data in \
  228.                             printer_jobs.get (printer, {}).iteritems ():
  229.                         state = data.get ('job-state',
  230.                                           cups.IPP_JOB_CANCELED)
  231.                         if state == cups.IPP_JOB_PROCESSING:
  232.                             have_processing_job = True
  233.                             break
  234.  
  235.                     if not have_processing_job:
  236.                         debugprint ("Ignoring stale connecting-to-device x")
  237.                         continue
  238.  
  239.                     # Build a new connecting_to_device dict.  If our existing
  240.                     # dict already has an entry for this printer, use that.
  241.                     printer = reason.get_printer ()
  242.                     t = self.connecting_to_device.get (printer, time_now)
  243.                     connecting_to_device[printer] = t
  244.                     debugprint ("Connecting time: %d" % (time_now - t))
  245.                     if time_now - t >= CONNECTING_TIMEOUT:
  246.                         if have_processing_job:
  247.                             if printer not in self.still_connecting:
  248.                                 self.still_connecting.add (printer)
  249.                                 self.watcher.still_connecting (self, reason)
  250.                             if self.connecting_timers.has_key (printer):
  251.                                 gobject.source_remove (self.connecting_timers
  252.                                                        [printer])
  253.                                 del self.connecting_timers[printer]
  254.                                 debugprint ("Stopped connecting timer "
  255.                                             "for `%s'" % printer)
  256.  
  257.                     connected = False
  258.                     break
  259.  
  260.             if connected and self.connecting_timers.has_key (printer):
  261.                 gobject.source_remove (self.connecting_timers[printer])
  262.                 del self.connecting_timers[printer]
  263.                 debugprint ("Stopped connecting timer for `%s'" % printer)
  264.  
  265.         # Clear any previously-notified errors that are now fine.
  266.         remove = set()
  267.         for printer in self.still_connecting:
  268.             if not connecting_to_device.has_key (printer):
  269.                 remove.add (printer)
  270.                 self.watcher.now_connected (self, printer)
  271.                 if self.connecting_timers.has_key (printer):
  272.                     gobject.source_remove (self.connecting_timers[printer])
  273.                     del self.connecting_timers[printer]
  274.                     debugprint ("Stopped connecting timer for `%s'" % printer)
  275.  
  276.         self.still_connecting = self.still_connecting.difference (remove)
  277.         self.connecting_to_device = connecting_to_device
  278.  
  279.     def check_state_reasons(self, my_printers=set(), printer_jobs={}):
  280.         # Look for any new reasons since we last checked.
  281.         old_reasons_seen_keys = self.reasons_seen.keys ()
  282.         reasons_now = set()
  283.         for printer, reasons in self.printer_state_reasons.iteritems ():
  284.             for reason in reasons:
  285.                 tuple = reason.get_tuple ()
  286.                 printer = reason.get_printer ()
  287.                 reasons_now.add (tuple)
  288.                 if not self.reasons_seen.has_key (tuple):
  289.                     # New reason.
  290.                     self.watcher.state_reason_added (self, reason)
  291.                     self.reasons_seen[tuple] = reason
  292.  
  293.                 if (reason.get_reason () == "connecting-to-device" and
  294.                     not self.connecting_to_device.has_key (printer)):
  295.                     # First time we've seen this.
  296.  
  297.                     have_processing_job = False
  298.                     for job, data in \
  299.                             printer_jobs.get (printer, {}).iteritems ():
  300.                         state = data.get ('job-state',
  301.                                           cups.IPP_JOB_CANCELED)
  302.                         if state == cups.IPP_JOB_PROCESSING:
  303.                             have_processing_job = True
  304.                             break
  305.  
  306.                     if have_processing_job:
  307.                         t = gobject.timeout_add ((1 + CONNECTING_TIMEOUT)
  308.                                                  * 1000,
  309.                                                  self.check_still_connecting,
  310.                                                  printer)
  311.                         self.connecting_timers[printer] = t
  312.                         debugprint ("Start connecting timer for `%s'" %
  313.                                     printer)
  314.                     else:
  315.                         # Don't notify about this, as it must be stale.
  316.                         debugprint ("Ignoring stale connecting-to-device")
  317.                         if get_debugging ():
  318.                             debugprint (pprint.pformat (printer_jobs))
  319.  
  320.         self.update_connecting_devices (printer_jobs)
  321.         items = self.reasons_seen.keys ()
  322.         for tuple in items:
  323.             if not tuple in reasons_now:
  324.                 # Reason no longer present.
  325.                 reason = self.reasons_seen[tuple]
  326.                 del self.reasons_seen[tuple]
  327.                 self.watcher.state_reason_removed (self, reason)
  328.  
  329.     def get_notifications(self):
  330.         if not self.process_pending_events:
  331.             # Defer the timer callback.
  332.             gobject.source_remove (self.update_timer)
  333.             self.update_timer = gobject.timeout_add (200,
  334.                                                      self.get_notifications)
  335.             return False
  336.  
  337.         debugprint ("get_notifications")
  338.         user = cups.getUser ()
  339.         try:
  340.             cups.setUser (self.user)
  341.             c = cups.Connection (host=self.host,
  342.                                  port=self.port,
  343.                                  encryption=self.encryption)
  344.  
  345.             try:
  346.                 try:
  347.                     notifications = c.getNotifications ([self.sub_id],
  348.                                                         [self.sub_seq + 1])
  349.                 except AttributeError:
  350.                     notifications = c.getNotifications ([self.sub_id])
  351.             except cups.IPPError, (e, m):
  352.                 cups.setUser (user)
  353.                 if e == cups.IPP_NOT_FOUND:
  354.                     # Subscription lease has expired.
  355.                     self.sub_id = -1
  356.                     self.refresh ()
  357.                     return False
  358.  
  359.                 self.watcher.cups_ipp_error (self, e, m)
  360.                 return True
  361.         except RuntimeError:
  362.             cups.setUser (user)
  363.             self.watcher.cups_connection_error (self)
  364.             return True
  365.  
  366.         cups.setUser (user)
  367.         deferred_calls = []
  368.         jobs = self.jobs.copy ()
  369.         for event in notifications['events']:
  370.             seq = event['notify-sequence-number']
  371.             self.sub_seq = seq
  372.             nse = event['notify-subscribed-event']
  373.             debugprint ("%d %s %s" % (seq, nse, event['notify-text']))
  374.             if get_debugging ():
  375.                 debugprint (pprint.pformat (event))
  376.             if nse.startswith ('printer-'):
  377.                 # Printer events
  378.                 name = event['printer-name']
  379.                 if nse == 'printer-added' and name not in self.printers:
  380.                     self.printers.add (name)
  381.                     deferred_calls.append ((self.watcher.printer_added,
  382.                                             (self, name)))
  383.  
  384.                 elif nse == 'printer-deleted' and name in self.printers:
  385.                     self.printers.remove (name)
  386.                     items = self.reasons_seen.keys ()
  387.                     for tuple in items:
  388.                         if tuple[1] == name:
  389.                             reason = self.reasons_seen[tuple]
  390.                             del self.reasons_seen[tuple]
  391.                             deferred_calls.append ((self.watcher.state_reason_removed,
  392.                                                     (self, reason)))
  393.                             
  394.                     if self.printer_state_reasons.has_key (name):
  395.                         del self.printer_state_reasons[name]
  396.  
  397.                     deferred_calls.append ((self.watcher.printer_removed,
  398.                                             (self, name)))
  399.                 elif name in self.printers:
  400.                     printer_state_reasons = event['printer-state-reasons']
  401.                     reasons = []
  402.                     for reason in printer_state_reasons:
  403.                         if reason == "none":
  404.                             break
  405.                         if state_reason_is_harmless (reason):
  406.                             continue
  407.                         reasons.append (StateReason (name, reason))
  408.                     self.printer_state_reasons[name] = reasons
  409.  
  410.                     deferred_calls.append ((self.watcher.printer_event,
  411.                                             (self, name, nse, event)))
  412.                 continue
  413.  
  414.             # Job events
  415.             jobid = event['notify-job-id']
  416.             if (nse == 'job-created' or
  417.                 (nse == 'job-state-changed' and
  418.                  not jobs.has_key (jobid) and
  419.                  event['job-state'] == cups.IPP_JOB_PROCESSING)):
  420.                 if (self.specific_dests != None and
  421.                     event['printer-name'] not in self.specific_dests):
  422.                     continue
  423.  
  424.                 try:
  425.                     attrs = c.getJobAttributes (jobid)
  426.                     if (self.my_jobs and
  427.                         attrs['job-originating-user-name'] != cups.getUser ()):
  428.                         continue
  429.  
  430.                     jobs[jobid] = attrs
  431.                 except AttributeError:
  432.                     jobs[jobid] = {'job-k-octets': 0}
  433.                 except cups.IPPError, (e, m):
  434.                     self.watcher.cups_ipp_error (self, e, m)
  435.                     jobs[jobid] = {'job-k-octets': 0}
  436.  
  437.                 deferred_calls.append ((self.watcher.job_added,
  438.                                         (self, jobid, nse, event,
  439.                                          jobs[jobid].copy ())))
  440.             elif (nse == 'job-completed' or
  441.                   (nse == 'job-state-changed' and
  442.                    event['job-state'] == cups.IPP_JOB_COMPLETED)):
  443.                 if not (self.which_jobs in ['completed', 'all']):
  444.                     try:
  445.                         del jobs[jobid]
  446.                         deferred_calls.append ((self.watcher.job_removed,
  447.                                                 (self, jobid, nse, event)))
  448.                     except KeyError:
  449.                         pass
  450.                     continue
  451.  
  452.             try:
  453.                 job = jobs[jobid]
  454.             except KeyError:
  455.                 continue
  456.  
  457.             for attribute in ['job-state',
  458.                               'job-name']:
  459.                 job[attribute] = event[attribute]
  460.             if event.has_key ('notify-printer-uri'):
  461.                 job['job-printer-uri'] = event['notify-printer-uri']
  462.  
  463.             deferred_calls.append ((self.watcher.job_event,
  464.                                    (self, jobid, nse, event, job.copy ())))
  465.  
  466.         self.set_process_pending (False)
  467.         self.update_jobs (jobs)
  468.         self.jobs = jobs
  469.  
  470.         for (fn, args) in deferred_calls:
  471.             fn (*args)
  472.         self.set_process_pending (True)
  473.  
  474.         # Update again when we're told to.  If we're getting CUPS
  475.         # D-Bus signals, however, rely on those instead.
  476.         if not self.received_any_dbus_signals:
  477.             gobject.source_remove (self.update_timer)
  478.             interval = 1000 * notifications['notify-get-interval']
  479.             self.update_timer = gobject.timeout_add (interval,
  480.                                                      self.get_notifications)
  481.  
  482.         return False
  483.  
  484.     def refresh(self, which_jobs=None, refresh_all=True):
  485.         debugprint ("refresh")
  486.  
  487.         if which_jobs != None:
  488.             self.which_jobs = which_jobs
  489.  
  490.         user = cups.getUser ()
  491.         try:
  492.             cups.setUser (self.user)
  493.             c = cups.Connection (host=self.host,
  494.                                  port=self.port,
  495.                                  encryption=self.encryption)
  496.         except RuntimeError:
  497.             self.watcher.cups_connection_error (self)
  498.             cups.setUser (user)
  499.             return
  500.  
  501.         if self.sub_id != -1:
  502.             try:
  503.                 c.cancelSubscription (self.sub_id)
  504.             except cups.IPPError, (e, m):
  505.                 self.watcher.cups_ipp_error (self, e, m)
  506.  
  507.             gobject.source_remove (self.update_timer)
  508.             debugprint ("Canceled subscription %d" % self.sub_id)
  509.  
  510.         try:
  511.             del self.sub_seq
  512.         except AttributeError:
  513.             pass
  514.  
  515.         events = ["printer-added",
  516.                   "printer-deleted",
  517.                   "printer-state-changed"]
  518.         if self.monitor_jobs:
  519.             events.extend (["job-created",
  520.                             "job-completed",
  521.                             "job-stopped",
  522.                             "job-state-changed"])
  523.  
  524.         try:
  525.             self.sub_id = c.createSubscription ("/", events=events)
  526.         except cups.IPPError, (e, m):
  527.             self.watcher.cups_ipp_error (self, e, m)
  528.  
  529.         cups.setUser (user)
  530.  
  531.         self.update_timer = gobject.timeout_add (MIN_REFRESH_INTERVAL * 1000,
  532.                                                  self.get_notifications)
  533.         debugprint ("Created subscription %d" % self.sub_id)
  534.  
  535.         if self.monitor_jobs:
  536.             jobs = self.jobs.copy ()
  537.             if self.which_jobs not in ['all', 'completed']:
  538.                 # Filter out completed jobs.
  539.                 filtered = {}
  540.                 for jobid, job in jobs.iteritems ():
  541.                     if job['job-state'] < cups.IPP_JOB_CANCELED:
  542.                         filtered[jobid] = job
  543.                 jobs = filtered
  544.  
  545.             self.fetch_first_job_id = 1
  546.             if self.fetch_jobs_timer:
  547.                 gobject.source_remove (self.fetch_jobs_timer)
  548.             self.fetch_jobs_timer = gobject.timeout_add (5, self.fetch_jobs,
  549.                                                          refresh_all)
  550.         else:
  551.             jobs = {}
  552.  
  553.         try:
  554.             self.printer_state_reasons = collect_printer_state_reasons (c)
  555.             dests = c.getPrinters ()
  556.             self.printers = set(dests.keys ())
  557.         except cups.IPPError, (e, m):
  558.             self.watcher.cups_ipp_error (self, e, m)
  559.             return
  560.         except RuntimeError:
  561.             self.watcher.cups_connection_error (self)
  562.             return
  563.  
  564.         if self.specific_dests != None:
  565.             for jobid in jobs.keys ():
  566.                 uri = jobs[jobid].get('job-printer-uri', '/')
  567.                 i = uri.rfind ('/')
  568.                 printer = uri[i + 1:]
  569.                 if printer not in self.specific_dests:
  570.                     del jobs[jobid]
  571.  
  572.         self.set_process_pending (False)
  573.         self.watcher.current_printers_and_jobs (self, self.printers.copy (),
  574.                                                 jobs.copy ())
  575.         self.update_jobs (jobs)
  576.         self.jobs = jobs
  577.         self.set_process_pending (True)
  578.         return False
  579.  
  580.     def fetch_jobs (self, refresh_all):
  581.         if not self.process_pending_events:
  582.             # Skip this call.  We'll get called again soon.
  583.             return True
  584.  
  585.         user = cups.getUser ()
  586.         try:
  587.             cups.setUser (self.user)
  588.             c = cups.Connection (host=self.host,
  589.                                  port=self.port,
  590.                                  encryption=self.encryption)
  591.         except RuntimeError:
  592.             self.watcher.cups_connection_error (self)
  593.             self.fetch_jobs_timer = None
  594.             cups.setUser (user)
  595.             return False
  596.  
  597.         limit = 1
  598.         try:
  599.             fetched = c.getJobs (which_jobs=self.which_jobs,
  600.                                  my_jobs=self.my_jobs,
  601.                                  first_job_id=self.fetch_first_job_id,
  602.                                  limit=limit)
  603.         except cups.IPPError, (e, m):
  604.             self.watcher.cups_ipp_error (self, e, m)
  605.             self.fetch_jobs_timer = None
  606.             cups.setUser (user)
  607.             return False
  608.  
  609.         cups.setUser (user)
  610.         got = len (fetched)
  611.         debugprint ("Got %s jobs, asked for %s" % (got, limit))
  612.  
  613.         deferred_calls = []
  614.         jobs = self.jobs.copy ()
  615.         jobids = fetched.keys ()
  616.         jobids.sort ()
  617.         if got > 0:
  618.             last_jobid = jobids[got - 1]
  619.         else:
  620.             last_jobid = self.fetch_first_job_id + limit
  621.         for jobid in xrange (self.fetch_first_job_id, last_jobid + 1):
  622.             try:
  623.                 job = fetched[jobid]
  624.                 if self.specific_dests != None:
  625.                     uri = job.get('job-printer-uri', '/')
  626.                     i = uri.rfind ('/')
  627.                     printer = uri[i + 1:]
  628.                     if printer not in self.specific_dests:
  629.                         raise KeyError
  630.  
  631.                 if jobs.has_key (jobid):
  632.                     fn = self.watcher.job_event
  633.                 else:
  634.                     fn = self.watcher.job_added
  635.  
  636.                 jobs[jobid] = job
  637.                 deferred_calls.append ((fn,
  638.                                         (self, jobid, '', {}, job.copy ())))
  639.             except KeyError:
  640.                 # No job by that ID.
  641.                 if jobs.has_key (jobid):
  642.                     del jobs[jobid]
  643.                     deferred_calls.append ((self.watcher.job_removed,
  644.                                             (self, jobid, '', {})))
  645.  
  646.         jobids = jobs.keys ()
  647.         jobids.sort ()
  648.         if got < limit:
  649.             trim = False
  650.             for i in range (len (jobids)):
  651.                 jobid = jobids[i]
  652.                 if not trim and jobid > last_jobid:
  653.                     trim = True
  654.             
  655.                 if trim:
  656.                     del jobs[jobid]
  657.                     deferred_calls.append ((self.watcher.job_removed,
  658.                                             (self, jobid, '', {})))
  659.  
  660.         self.update_jobs (jobs)
  661.         self.jobs = jobs
  662.  
  663.         for (fn, args) in deferred_calls:
  664.             fn (*args)
  665.  
  666.         if got < limit:
  667.             # That's all.  Don't run this timer again.
  668.             self.fetch_jobs_timer = None
  669.             return False
  670.  
  671.         # Remember where we got up to and run this timer again.
  672.         next = jobid + 1
  673.  
  674.         while not refresh_all and self.jobs.has_key (next):
  675.             next += 1
  676.  
  677.         self.fetch_first_job_id = next
  678.         return True
  679.  
  680.     def sort_jobs_by_printer (self, jobs=None):
  681.         if jobs == None:
  682.             jobs = self.jobs
  683.  
  684.         my_printers = set()
  685.         printer_jobs = {}
  686.         for job, data in jobs.iteritems ():
  687.             state = data.get ('job-state', cups.IPP_JOB_CANCELED)
  688.             if state >= cups.IPP_JOB_CANCELED:
  689.                 continue
  690.             uri = data.get ('job-printer-uri', '')
  691.             i = uri.rfind ('/')
  692.             if i == -1:
  693.                 continue
  694.             printer = uri[i + 1:]
  695.             my_printers.add (printer)
  696.             if not printer_jobs.has_key (printer):
  697.                 printer_jobs[printer] = {}
  698.             printer_jobs[printer][job] = data
  699.  
  700.         return (printer_jobs, my_printers)
  701.  
  702.     def update_jobs(self, jobs):
  703.         debugprint ("update_jobs")
  704.         (printer_jobs, my_printers) = self.sort_jobs_by_printer (jobs)
  705.         self.check_state_reasons (my_printers, printer_jobs)
  706.  
  707.     def update(self):
  708.         gobject.source_remove (self.update_timer)
  709.         self.update_timer = gobject.timeout_add (200, self.get_notifications)
  710.  
  711.     def handle_dbus_signal(self, *args):
  712.         self.update ()
  713.         if not self.received_any_dbus_signals:
  714.             self.received_any_dbus_signals = True
  715.  
  716. if __name__ == '__main__':
  717.     set_debugging (True)
  718.     m = Monitor (Watcher ())
  719.     loop = gobject.MainLoop ()
  720.     try:
  721.         loop.run ()
  722.     finally:
  723.         m.cleanup ()
  724.